package edu.amm.fanal.methods;

import org.apache.log4j.Logger;

import edu.amm.fanal.api.BoundaryProblem;
import edu.amm.fanal.api.Function;
import edu.amm.fanal.api.LebesgueSpace;
import edu.amm.fanal.api.LinearProblem;
import edu.amm.fanal.api.NonlinearProblem;

/**
 * Реализация метода Ньютона-Канторовича (НЕмодифицированного) для решения
 * краевой задачи для нелинейного обыкновенного дифференциального
 * уравнения второго порядка.
 * 
 * @author Иванов Илья
 * @since 2013-05-10
 *
 */
public class NewtonKantorovichMethod extends Method<NonlinearProblem> {
	
	private MomentsMethod momentsMethod;
	private Function x0 = Function.createIdentical(BoundaryProblem.SOLUTION_DIM, 0);
	private int iterations = DEFAULT_ITERATIONS_COUNT;
	
	/**
	 * @param momentsMethod Метод моментов для решения линейной задачи.
	 */
	public NewtonKantorovichMethod(MomentsMethod momentsMethod) {
		if (momentsMethod == null)
			throw new RuntimeException("Не задан метод моментов для решения линейной задачи");
		
		this.momentsMethod = momentsMethod;
	}
	
	/**
	 * @param initialApproximation Начальное приближение.
	 */
	public void setInitialApproximation(Function initialApproximation) {
		Function.checkFunction(initialApproximation, "\"начальное приближение\"", BoundaryProblem.SOLUTION_DIM);
		this.x0 = initialApproximation;
	}
	
	/**
	 * @return Начальное приближение. По умолчанию тождественно равно нулю.
	 */
	public Function getInitialApproximation() {
		return x0;
	}
	
	/**
	 * @param iterations Число итераций работы метода.
	 */
	public void setIterationsCount(int iterations) {
		if (iterations < 1)
			throw new RuntimeException("Количество итераций должно быть положительным");
		
		this.iterations = iterations;
	}
	
	/**
	 * @return Число итераций работы метода.
	 * По умолчанию равно {@linkplain #DEFAULT_ITERATIONS_COUNT}.
	 */
	public int getIterationsCount() {
		return iterations;
	}
	
	protected Function solveProblem(NonlinearProblem problem) {
		long start = System.currentTimeMillis();
		
		String msg = "Метод Ньютона-Канторовича. Решение нелинейной задачи на отрезке " +
			"[%f; %f] с краевыми условиями (%f; %f)";
		log.info(String.format(msg, problem.getLeftBound(), problem.getRightBound(),
			problem.getLeftValue(), problem.getRightValue()));
		
		final Function f = problem.getF();
		final Function dfdx = f.differentiate(1);
		final Function dfdxdt = f.differentiate(2);
		
		int iteration = 0;
		Function solution = x0;
		while (iteration++ < iterations) {
			String logPrefix = String.format("Метод Ньютона-Канторовича. Итерация: %d.", iteration);
			log.info("************************ " + logPrefix + " ************************");
			
			final Function xn = solution;
			final Function dxndt = xn.differentiate(0);
			
			final Function p = new Function(BoundaryProblem.SOLUTION_DIM) {
				
				protected double getValue(double... x) {
					return - dfdxdt.evaluate(x[0], xn.evaluate(x), dxndt.evaluate(x));
				}
			};
			
			final Function q = new Function(BoundaryProblem.SOLUTION_DIM) {
				
				protected double getValue(double... x) {
					return - dfdx.evaluate(x[0], xn.evaluate(x), dxndt.evaluate(x));
				}
			};
			
			Function r = new Function(BoundaryProblem.SOLUTION_DIM) {
				
				protected double getValue(double... x) {
					return f.evaluate(x[0], xn.evaluate(x[0]), dxndt.evaluate(x[0]))
						+ q.evaluate(x) * xn.evaluate(x[0])
						+ p.evaluate(x) * dxndt.evaluate(x[0]);
				}
			};
			
			LinearProblem linear = new LinearProblem(problem.getLeftBound(),
				problem.getLeftValue(), problem.getRightBound(), problem.getRightValue());
			linear.setP(p);
			linear.setQ(q);
			linear.setF(r);
			
			solution = momentsMethod.solve(linear);
			if (problem.getSolution() != null) {
				double accuracy = new LebesgueSpace(problem).norm(Function.subtraction(problem.getSolution(), solution));
				log.info(String.format(logPrefix + " Точность полученного решения: %.17f", accuracy));
			}
		}
		
		long elapsed = System.currentTimeMillis() - start;
		log.info(String.format("Время решения задачи: %d секунд", elapsed / 1000));
		
		return solution;
	}
	
	private static Logger log = Logger.getLogger(NewtonKantorovichMethod.class);
	
	private static final int DEFAULT_ITERATIONS_COUNT = 3;
}